
from vrpsc.Util.generate_init_solution import generate_solution_greedy,generate_solution_nearest,generate_solution_random
from vrpsc.Util.util import *
import random
import numpy as np
from vrpsc.Util.Solution import Solution
from vrpsc.Util.load_data import *
from vrpsc.ALNS import _calculate_and_package_metrics

POP_SIZE = 100  # 种群规模
CROSSOVER_RATE = 0.9  # 交叉概率
MUTATION_RATE = 0.1  # 变异概率


def get_task_position(path_map, task):
    for path_index, path in path_map.items():
        try:
            index = path.index(task)
            if path_index <= Config.M_parent:
                parent_path_index = path_index
                parent_path_task_index = index
            else:
                child_path_index = path_index
                child_path_task_index = index
        except ValueError:
            continue
    return parent_path_index, parent_path_task_index, child_path_index, child_path_task_index

def cross_over_task(path_map_1, path_map_2, task):
    """
    点交叉，把path_map_1中task的位置调整为task在path_map_2中的位置
    :param solution_1:
    :param solution_2:
    :param task:
    :return:
    """
    # path_map_1 = solution_1.get_path_map()
    # path_map_2 = solution_2.get_path_map()

    parent_path_index_1, parent_path_task_index_1, child_path_index_1, child_path_task_index_1 = get_task_position(path_map_1, task)
    parent_path_index_2, parent_path_task_index_2, child_path_index_2, child_path_task_index_2 = get_task_position(path_map_2, task)

    path_map_1[parent_path_index_1].remove(task)
    path_map_1[parent_path_index_2].insert(parent_path_task_index_2, task)

    path_map_1[child_path_index_1].remove(task)
    path_map_1[child_path_index_2].insert(child_path_task_index_2, task)

    return path_map_1

def crossover_and_mutation(pop):
    new_pop = []
    for father in pop:
        child = father
        if np.random.rand() < CROSSOVER_RATE:
            mother = random.choice(pop)
            child = cross_over_solution(father, mother)
        child = mutation(child)
        new_pop.append(child)
    return new_pop

def mutation(child):
    if np.random.rand() < MUTATION_RATE:
        return get_neighbor_solution(child)
    return child

def neighbor_insertion(solution):
    task = random.randint(1, solution.task_num)
    sequence_map = solution.get_sequence_map()
    path_init_task_map = solution.get_path_init_task_map()

    remove_(sequence_map, path_init_task_map, task)

    parent_positions = get_all_position(sequence_map, path_init_task_map, task, 'parent')

    parent_position = random.sample(parent_positions, 1)[0]
    insert_(sequence_map, path_init_task_map, task, parent_position, 'parent')

    child_positions = get_feasible_insert_position(sequence_map, path_init_task_map, task, 'child')
    child_position = random.sample(child_positions, 1)[0]
    insert_(sequence_map, path_init_task_map, task, child_position, 'child')
    return Solution(solution.instance, sequence_map, path_init_task_map)



def neighbor_swap(solution):
    sequence_map = solution.get_sequence_map()
    path_init_task_map = solution.get_path_init_task_map()
    task_list = list(range(1, solution.task_num + 1))
    task_1 = random.sample(task_list, 1)[0]
    task_list.remove(task_1)
    task_2 = random.sample(task_list, 1)[0]
    '''如果任务是某个AGV的第一个任务，更新path_init_task_map'''
    if sequence_map[task_1]['parent_pre_task'] == 0:
        path_init_task_map[sequence_map[task_1]['parent']] = task_2

    if sequence_map[task_1]['child_pre_task'] == 0:
        path_init_task_map[sequence_map[task_1]['child']] = task_2

    if sequence_map[task_2]['parent_pre_task'] == 0:
        path_init_task_map[sequence_map[task_2]['parent']] = task_1

    if sequence_map[task_2]['child_pre_task'] == 0:
        path_init_task_map[sequence_map[task_2]['child']] = task_1

    '''交换task_1和task_2'''
    task_1_parent = sequence_map[task_1]['parent']
    task_1_parent_pre_task = sequence_map[task_1]['parent_pre_task']
    task_1_parent_next_task = sequence_map[task_1]['parent_next_task']
    task_1_child = sequence_map[task_1]['child']
    task_1_child_pre_task = sequence_map[task_1]['child_pre_task']
    task_1_child_next_task = sequence_map[task_1]['child_next_task']

    if sequence_map[task_1]['parent_pre_task'] != 0 and sequence_map[task_1]['parent_pre_task'] != task_2:
        sequence_map[sequence_map[task_1]['parent_pre_task']]['parent_next_task'] = task_2

    if sequence_map[task_1]['parent_next_task'] != 0 and sequence_map[task_1]['parent_next_task'] != task_2:
        sequence_map[sequence_map[task_1]['parent_next_task']]['parent_pre_task'] = task_2

    if sequence_map[task_1]['child_pre_task'] != 0 and sequence_map[task_1]['child_pre_task'] != task_2:
        sequence_map[sequence_map[task_1]['child_pre_task']]['child_next_task'] = task_2

    if sequence_map[task_1]['child_next_task'] != 0 and sequence_map[task_1]['child_next_task'] != task_2:
        sequence_map[sequence_map[task_1]['child_next_task']]['child_pre_task'] = task_2

    ''''''

    if sequence_map[task_2]['parent_pre_task'] != 0 and sequence_map[task_2]['parent_pre_task'] != task_1:
        sequence_map[sequence_map[task_2]['parent_pre_task']]['parent_next_task'] = task_1

    if sequence_map[task_2]['parent_next_task'] != 0 and sequence_map[task_2]['parent_next_task'] != task_1:
        sequence_map[sequence_map[task_2]['parent_next_task']]['parent_pre_task'] = task_1

    if sequence_map[task_2]['child_pre_task'] != 0 and sequence_map[task_2]['child_pre_task'] != task_1:
        sequence_map[sequence_map[task_2]['child_pre_task']]['child_next_task'] = task_1

    if sequence_map[task_2]['child_next_task'] != 0 and sequence_map[task_2]['child_next_task'] != task_1:
        sequence_map[sequence_map[task_2]['child_next_task']]['child_pre_task'] = task_1

    sequence_map[task_1]['parent'] = sequence_map[task_2]['parent']
    if sequence_map[task_2]['parent_pre_task'] == task_1:
        sequence_map[task_1]['parent_pre_task'] = task_2
    else:
        sequence_map[task_1]['parent_pre_task'] = sequence_map[task_2]['parent_pre_task']

    if sequence_map[task_2]['parent_next_task'] == task_1:
        sequence_map[task_1]['parent_next_task'] = task_2
    else:
        sequence_map[task_1]['parent_next_task'] = sequence_map[task_2]['parent_next_task']

    sequence_map[task_1]['child'] = sequence_map[task_2]['child']

    if sequence_map[task_2]['child_pre_task'] == task_1:
        sequence_map[task_1]['child_pre_task'] = task_2
    else:
        sequence_map[task_1]['child_pre_task'] = sequence_map[task_2]['child_pre_task']

    if sequence_map[task_2]['child_next_task'] == task_1:
        sequence_map[task_1]['child_next_task'] = task_2
    else:
        sequence_map[task_1]['child_next_task'] = sequence_map[task_2]['child_next_task']


    sequence_map[task_2]['parent'] = task_1_parent

    if task_1_parent_pre_task == task_2:
        sequence_map[task_2]['parent_pre_task'] = task_1
    else:
        sequence_map[task_2]['parent_pre_task'] = task_1_parent_pre_task

    if task_1_parent_next_task == task_2:
        sequence_map[task_2]['parent_next_task'] = task_1
    else:
        sequence_map[task_2]['parent_next_task'] = task_1_parent_next_task

    sequence_map[task_2]['child'] = task_1_child

    if task_1_child_pre_task == task_2:
        sequence_map[task_2]['child_pre_task'] = task_1
    else:
        sequence_map[task_2]['child_pre_task'] = task_1_child_pre_task

    if task_1_child_next_task == task_2:
        sequence_map[task_2]['child_next_task'] = task_1
    else:
        sequence_map[task_2]['child_next_task'] = task_1_child_next_task

    return Solution(solution.instance, sequence_map, path_init_task_map)

def get_neighbor_solution(solution):
    neighbor_list = [neighbor_insertion, neighbor_swap]
    neighbor_index = random.randint(0,1)
    neighbor_solution = neighbor_list[neighbor_index](solution)
    return neighbor_solution
def cross_over_solution(solution_1, solution_2):
    """
    调整solution1的path_map，按照solution2的path_map的任务位置
    :param solution_1:
    :param solution_2:
    :return:
    """
    task_num = solution_1.task_num
    task_list = list(range(1, task_num + 1))
    random.shuffle(task_list)
    path_map_cross = solution_1.get_path_map()
    path_map_2 = solution_2.get_path_map()
    cross_task_num = 0
    for task in task_list:
        path_map_cross_temp = copy_dict_int_list(path_map_cross)
        path_map_cross_temp = cross_over_task(path_map_cross_temp, path_map_2, task)
        sequence_map, path_init_task_map = path_map2sequence_map(path_map_cross_temp)
        fitness, feasible = cal_fitness_feasible(solution_1.instance, sequence_map, path_init_task_map)
        if feasible:
            path_map_cross = path_map_cross_temp
            cross_task_num += 1
        if cross_task_num >= 1:
            break
    sequence_map, path_init_task_map = path_map2sequence_map(path_map_cross)
    return Solution(solution_1.instance, sequence_map, path_init_task_map)

def GA(instance_name):
    instance = read_excel(instance_name)
    task_num = len(instance)
    pop = []
    best_solution = generate_solution_greedy(instance)
    best_fitness = best_solution.get_fitness()
    pop.append(best_solution)

    for _ in range(int(POP_SIZE / 2)):
        # p = 0.01 + (1 / (POP_SIZE / 2 + 1))
        solution_random = generate_solution_nearest(instance, random.random())
        pop.append(solution_random)
    for _ in range(int(POP_SIZE / 2), POP_SIZE - 1):
        solution_random = generate_solution_random(instance)
        pop.append(solution_random)


    start_time = time.time()
    count = 0

    duration = task_num ** 2 * (Config.M_child + Config.M_parent) * 10 / 1000

    # while time.time() - start_time < duration:
    while count < 100:
        count += 1
        for solution in pop:
            fitness = solution.get_fitness()
            if fitness < best_fitness:
                best_solution = solution
                best_fitness = fitness


        new_pop = crossover_and_mutation(pop)
        pop = select(new_pop)

        pop[random.randint(0, len(pop) - 1)] = best_solution  # 精英保留策略
        # print(f"第:{count}次迭代，best_fitness:{best_fitness}, time:{time.time() - start_time}")

    return best_solution

def select(pop):
    fitness_list = np.array([s.get_fitness() for s in pop])
    # hash_key_list = np.array([hash_key for hash_key in pop_map.keys()])
    # fitness_list = np.array([pop_map[hash_key].get_fitness() for hash_key in hash_key_list])
    for j in range(len(fitness_list)):
        fitness_list[j] = - fitness_list[j]

    fitness_list = (fitness_list - np.min(fitness_list)) + 1e-3
    idx = np.random.choice(np.arange(POP_SIZE), size=POP_SIZE, replace=True,
                           p=(fitness_list) / (fitness_list.sum()))
    return np.array(pop)[idx]


def run_GA_experiment(instance_name, repeated):
    runtime_start_total = time.time()

    # Lists to store metrics for ALNS solutions
    costs = []
    delay_costs = []
    dist_costs = []


    best_cost = float('inf')  # Initialize with a very large number
    best_solution_code = None  # Stores the actual code/representation of the best ALNS solution

    print(f"Starting GA for instance: {instance_name} with {repeated} repetitions...")

    for i in range(repeated):
        print(f"--- Repetition {i + 1}/{repeated} ---")

        # Execute the ALNS algorithm for the current repetition
        solution = GA(instance_name)

        # Process ALNS solution metrics
        costs.append(solution.get_fitness())
        dist_costs.append(solution.distance)
        delay_costs.append(solution.tardiness)

        # Update best_alns_cost and best_alns_solution_code if the current ALNS solution is better
        if solution.get_fitness() < best_cost:
            best_cost = solution.get_fitness()
            best_solution_code = solution.get_code()  # Store the code of the best ALNS solution found so far

    total_runtime = time.time() - runtime_start_total
    avg_runtime_per_repetition = total_runtime / repeated

    # Calculate and package metrics for ALNS solutions
    data_package = _calculate_and_package_metrics(
        costs,
        dist_costs,
        delay_costs,
        avg_runtime_per_repetition
    )
    # Update the first element of alns_data_package with the best solution code
    data_package[0] = best_solution_code
    return best_solution_code, data_package



# if __name__ == "__main__":
#     instance_name = "tasks_20"
#     solution = GA(instance_name)
#     print(f"fitness:{solution.get_fitness()}")
#     print(f"distance:{solution.distance}  tardiness:{solution.tardiness}")
#     pass
